using System;
using System.Collections;

namespace CardsEngine
{
    // Maybe this would be better as a struct?
	public class Card
	{
        // All of these values should be prime numbers
        // (used for generating hash codes)
        private const int HASH_START = 7;
        private const int HASH_FACE_MULTIPLIER = 19;
        private const int HASH_SUIT_MULTIPLIER = 29;

        /// <summary>
        /// Represents the types of values Aces can have:
        /// either high or low
        /// </summary>
		public enum AceMode { ACES_HIGH=0, ACES_LOW }

        /// <summary>
        /// Represents one of the ways in which Cards can be displayed
        /// and/or parsed in IO-type operations
        /// </summary>
        public enum CardFormat {
            /// <summary>
            /// A two-character long name in the form "Fs", 
            /// ex. "Th" for the Ten of Hearts; "4d" for the Four of Diamonds
            /// </summary>
            TWO_CHARACTERS,

            /// <summary>
            /// The full name of the card in the form "F of S",
            /// ex. "Ace of Spades" or "Two of Clubs"
            /// </summary>
            FULL_NAME }

		#region Nested Classes: Face and Suit

		public class Face
		{

			private string name;
			private int val;

			private Face(string _name, int _val)
			{
				name = _name;
				val = _val;
			}

			public static Face GetAce (AceMode mode)
			{
				switch (mode) {
				case AceMode.ACES_HIGH:
					return HIGH_ACE;
				
				case AceMode.ACES_LOW:
					return LOW_ACE;

				default:
					throw new NotImplementedException ();
				}
			}

            public static Face GetByValue(int x)
            {
                switch (x)
                {
                    case 0:
                        return JOKER;

                    case 1:
                        return LOW_ACE;

                    case 2:
                        return TWO;

                    case 3:
                        return THREE;

                    case 4:
                        return FOUR;

                    case 5:
                        return FIVE;

                    case 6:
                        return SIX;

                    case 7:
                        return SEVEN;

                    case 8:
                        return EIGHT;

                    case 9:
                        return NINE;

                    case 10:
                        return TEN;

                    case 11:
                        return JACK;

                    case 12:
                        return QUEEN;

                    case 13:
                        return KING;

                    case 14:
                        return HIGH_ACE;

                    default:
                        throw new NotSupportedException();
                }
            }

			public char ToChar ()
			{
				if (this == JOKER)
					return '*';
				else if (this == LOW_ACE || this == HIGH_ACE)
					return 'A';
				else if (this == TWO)
					return '2';
				else if (this == THREE)
					return '3';
				else if (this == FOUR)
					return '4';
				else if (this == FIVE)
					return '5';
				else if (this == SIX)
					return '6';
				else if (this == SEVEN)
					return '7';
				else if (this == EIGHT)
					return '8';
				else if (this == NINE)
					return '9';
				else if (this == TEN)
					return 'T';
				else if (this == JACK)
					return 'J';
				else if (this == QUEEN)
					return 'Q';
				else if (this == KING)
					return 'K';
				else
					throw new NotImplementedException();
			}

			public override string ToString ()
			{
				return this.Name;
			}

			#region Read Attribute Accessors

			public string Name  { get { return name; } }

			public int Value { get { return val; } }

			#endregion

			#region Parse Methods

			public static Face CharParse (char ch, AceMode mode = AceMode.ACES_HIGH)
			{
				Face result;

				if (!TryCharParse(ch, out result, mode))
					throw new FormatException();

				else
					return result;
			}

			public static bool TryCharParse (char ch, out Face x, AceMode mode = AceMode.ACES_HIGH)
			{
				switch (ch)
				{
				case 'A':
					x = ((mode == AceMode.ACES_HIGH) ? HIGH_ACE : LOW_ACE);
					break;
				
				case '2':
					x = TWO; break;

				case '3':
					x = THREE; break;

				case '4':
					x = FOUR; break;

				case '5':
					x = FIVE; break;

				case '6':
					x = SIX; break;

				case '7':
					x = SEVEN; break;

				case '8':
					x = EIGHT; break;

				case '9':
					x = NINE; break;

				case 'T':
					x = TEN; break;

				case 'J':
					x = JACK; break;

				case 'Q':
					x = QUEEN; break;

				case 'K':
					x = KING; break;

				default:
					x = null;
					return false;
				}

				return true;
			}

			public static Face Parse (string str, AceMode mode = AceMode.ACES_HIGH)
			{
				Face result;

				if (!TryParse(str, out result, mode))
					throw new FormatException();

				else
					return result;
			}

			public static bool TryParse (string _str, out Face x, AceMode mode = AceMode.ACES_HIGH)
			{
				string str = _str.ToLower();

				if (str.Equals ("joker")) {
					x = JOKER;
					return true;
				}

				if (str.Equals ("ace")) {
					x = GetAce (mode);
					return true;
				}

				if (str.Equals ("two")) {
					x = TWO;
					return true;
				}

				if (str.Equals ("three")) {
					x = THREE;
					return true;
				}

				if (str.Equals ("four")) {
					x = FOUR;
					return true;
				}

				if (str.Equals ("five")) {
					x = FIVE;
					return true;
				}

				if (str.Equals ("six")) {
					x = SIX;
					return true;
				}

				if (str.Equals ("seven")) {
					x = SEVEN;
					return true;
				}

				if (str.Equals ("eight")) {
					x = EIGHT;
					return true;
				}

				if (str.Equals ("nine")) {
					x = NINE;
					return true;
				}

				if (str.Equals ("ten")) {
					x = TEN;
					return true;
				}

				if (str.Equals ("jack")) {
					x = JACK;
					return true;
				}

				if (str.Equals ("queen")) {
					x = QUEEN;
					return true;
				}

				if (str.Equals ("king")) {
					x = KING;
					return true;
				}

				x = default(Face);
				return false;
			
			}
			
			#endregion

			#region Static Readonly Constants

			public static readonly Face JOKER = new Face("Joker",0);
			public static readonly Face LOW_ACE = new Face("Ace",1);
			public static readonly Face TWO = new Face("Two",2);
			public static readonly Face THREE = new Face("Three",3);
			public static readonly Face FOUR = new Face("Four",4);
			public static readonly Face FIVE = new Face("Five",5);
			public static readonly Face SIX = new Face("Six",6);
			public static readonly Face SEVEN = new Face("Seven",7);
			public static readonly Face EIGHT = new Face("Eight",8);
			public static readonly Face NINE = new Face("Nine",9);
			public static readonly Face TEN = new Face("Ten",10);
			public static readonly Face JACK = new Face("Jack",11);
			public static readonly Face QUEEN = new Face("Queen",12);
			public static readonly Face KING = new Face("King",13);
			public static readonly Face HIGH_ACE = new Face("Ace",14);

			#endregion

		}

		public class Suit
		{
			private string name;
			private int val;

			private Suit(string _name, int _val)
			{
				name = _name;
				val = _val;
			}


			public int Value { get { return val; } }
			public string Name { get { return name; } }

			public char ToChar ()
			{
				if (this == CLUBS)
					return 'c';
				else if (this == HEARTS)
					return 'h';
				else if (this == DIAMONDS)
					return 'd';
				else if (this == SPADES)
					return 's';
				else
					throw new NotImplementedException();
			}

			public override string ToString ()
			{
				return this.Name;
			}

			#region Parse Methods

			public static Suit CharParse (char ch)
			{
				Suit result;

				if (!TryCharParse(ch,out result))
					throw new FormatException();

				else
					return result;
			}

			public static bool TryCharParse (char ch, out Suit result)
			{
				switch (ch)
				{
				case 'c':
					result = CLUBS;
					break;

				case 'd':
					result = DIAMONDS;
					break;

				case 'h':
					result = HEARTS;
					break;

				case 's':
					result = SPADES;
					break;

				default:
					result = null;
					return false;
				}

				return true;
			}

			public static Suit Parse(string _str)
			{
				string str = _str.ToLower();

				if (str.Equals("clubs")) return CLUBS;
				if (str.Equals("diamonds")) return DIAMONDS;
				if (str.Equals("hearts")) return HEARTS;
				if (str.Equals("spades")) return SPADES;

				throw new FormatException();
			}

			public static bool TryParse (string str, out Suit x)
			{
				try {
					x = Parse (str);
					return true;
				} catch (FormatException) {
					x = null;
					return false;
				}
			}

			#endregion

			#region Suit-to-int and int-to-Suit casting operators

			public static explicit operator int (Suit suit)
			{
				return suit.Value;
			}

			public const int MINIMUM_INT_VALUE = 1;
			public const int MAXIMUM_INT_VALUE = 4;

			public static explicit operator Suit (int x)
			{
				switch (x) {
				case MINIMUM_INT_VALUE:
					return CLUBS;

				case MINIMUM_INT_VALUE+1:
					return HEARTS;

				case MINIMUM_INT_VALUE+2:
					return DIAMONDS;

				case MAXIMUM_INT_VALUE:
					return SPADES;

				default:
					throw new InvalidCastException ();
				}
			}

			#endregion

			#region Static Readonly Constants

			public static readonly Suit CLUBS = new Suit("Clubs",MINIMUM_INT_VALUE);
			public static readonly Suit HEARTS = new Suit("Hearts",MINIMUM_INT_VALUE+1);
			public static readonly Suit DIAMONDS = new Suit("Diamonds",MINIMUM_INT_VALUE+2);
			public static readonly Suit SPADES = new Suit("Spades",MAXIMUM_INT_VALUE);

			#endregion

		}

		#endregion 

        // Maybe in the future, use a lookup table to avoid the possible
        // need to ever generate more than one identical card
        //private static Hashtable lookupTable;

		private Face face;
		private Suit suit;

		public Card (Face _face, Suit _suit)
		{
			face = _face;
			suit = _suit;
		}

        public override int GetHashCode()
        {
            int result = HASH_START;

            unchecked
            {
                result += CardFace.GetHashCode() * HASH_FACE_MULTIPLIER;
                result = result * HASH_SUIT_MULTIPLIER + CardSuit.GetHashCode();
            }

            return result;
        }

        public override bool Equals(object obj)
        {
            Card other = obj as Card;

            if (other == null)
                return false;

            return (CardFace == other.CardFace && CardSuit == other.CardSuit);
        }

        public virtual int GetIntegerValue()
        {
            return face.Value;
        }

        /// <summary>
        /// Tries to parse a string as a Card, using the short (2-character) format.
        /// This is equivalent to Card.TryParse(_str, out result, aceMode, CardFormat.TWO_CHARACTERS)
        /// </summary>
        /// <param name="_str">The string to parse</param>
        /// <param name="result">Reference to location to store the parsed Card, if successful</param>
        /// <param name="aceMode">If the card parsed is an ace, it will either be HIGH_ACE or LOW_ACE depending on the value passed here.</param>
        /// <returns>An instance of Card representing the supplied 2-character string</returns>
        public static bool TryShortParse(string _str, out Card result, AceMode aceMode = AceMode.ACES_HIGH)
        {
            try
            {
                result = ShortParse(_str, aceMode);
            }
            catch (FormatException)
            {
                result = null;
                return false; 
            }

            return true;
        }

        /// <summary>
        /// Parses a string as a Card, using the short 2-character format.
        /// This is equivalent to Card.Parse(_str, out result, aceMode, CardFormat.TWO_CHARACTERS)
        /// </summary>
        /// <exception cref="System.FormatException">The string could not be parsed as a Card using the short 2-char parsing format</exception>
        /// <param name="_str">The string to parse</param>
        /// <param name="aceMode">If the card parsed is an ace, it will either be HIGH_ACE or LOW_ACE depending on the value passed here.</param>
        /// <returns>An instance of Card representing the supplied 2-character string</returns>
        public static Card ShortParse(string _str, AceMode aceMode = AceMode.ACES_HIGH)
        {
            Face f;
            Suit s;
            string str = _str.Trim();

            if ( str.Length !=2 || !Face.TryCharParse(str[0], out f) || !Suit.TryCharParse(str[1], out s) )
                throw new FormatException();

            return new Card(f,s);
        }

        /// <summary>
        /// Tries to parse a string as a Card, using the specified format and the default AceMode (ACES_HIGH).
        /// </summary>
        /// <param name="_str">The string to parse</param>
        /// <param name="result">Reference to location to store the parsed Card, if operation is successful</param>
        /// <param name="format">Indicates whether parse by looking for the full name, or the two-character abbreviation</param>
        /// <returns>True if operation was successful; false otherwise</returns>
        public static bool TryParse(string _str, out Card result, CardFormat format)
        {
            return TryParse(_str, out result, AceMode.ACES_HIGH, format);
        }

		/// <summary>
		/// Tries to parse a string as a Card, using the specified ace mode and parse format.
		/// </summary>
        /// <param name="_str">The string to parse</param>
        /// <param name="result">Reference to location to store the parsed Card, if operation is successful</param>
        /// <param name="aceMode">If the card parsed is an ace, it will either be HIGH_ACE or LOW_ACE depending on the value passed here</param>
        /// <param name="format">Indicates whether parse by looking for the full name, or the two-character abbreviation</param>
		/// <returns>True if operation was successful; false otherwise</returns>
		public static bool TryParse (string _str, out Card result, AceMode aceMode = AceMode.ACES_HIGH, CardFormat format = CardFormat.FULL_NAME)
		{
            string str = _str.Trim();

			try {
                switch (format)
                {
                    case CardFormat.FULL_NAME:
                        result = Parse(str,aceMode,format);
                        break;

                    case CardFormat.TWO_CHARACTERS:
                        result = ShortParse(str);
                        break;

                    default:
                        throw new NotImplementedException();
                }
			} catch (FormatException) {
				result = null;
				return false;
			}

			return true;
		}

        /// <summary>
        /// Parse the specified string as a Card, using the specified format and the default AceMode (ACES_HIGH).
        /// </summary>
        /// <param name="_str">The string to parse</param>
        /// <param name="format">Indicates whether parse by looking for the full name, or the two-character abbreviation</param>
        /// <exception cref="System.FormatException">The supplied string could not be parsed as a Card using the specified format</exception>
        /// <returns>An instance of Card representing the supplied string in the specified format</returns>
        public static Card Parse(string _str, CardFormat format)
        {
            return Parse(_str, AceMode.ACES_HIGH, format);
        }

		/// <summary>
        /// Parse the specified string as a Card, using the specified ace mode and format.
		/// </summary>
        /// <param name="_str">The string to parse</param>
        /// <param name="aceMode">If the card parsed is an ace, it will either be HIGH_ACE or LOW_ACE depending on the value passed here</param>
        /// <param name="format">Indicates whether parse by looking for the full name, or the two-character abbreviation</param>
        /// <exception cref="System.FormatException">The supplied string could not be parsed as a Card using the specified format</exception>
        /// <returns>An instance of Card representing the supplied string</returns>
		public static Card Parse (string _str, AceMode aceMode = AceMode.ACES_HIGH, CardFormat format = CardFormat.FULL_NAME)
		{
			string[] words = _str.Split (' ');
            Face f;
            Suit s;

			if (words.Length != 3) {
				throw new FormatException ("Wrong number of space-delimited words: must be 3 words (i.e. \"Jack of Spades\")");
			}

			try {
				f = Face.Parse (words [0], aceMode);
			} catch (FormatException ex) {
				throw new FormatException (string.Format ("Could not parse {0} as a Card.Face object", words [0]), ex);
			}

			try {
				s = Suit.Parse (words [2]);
			} catch (FormatException ex) {
				throw new FormatException(string.Format ("Could not parse {0} as a Card.Suit object", words[2]), ex);
			}

			return new Card(f, s);
		}

        /// <summary>
        /// Equivalent to calling ToString(CardFormat.TWO_CHARACTERS)
        /// </summary>
        /// <returns>A two-character string representation of this Card, ex. "Ts" (Ten of Spades)</returns>
		public string ToShortString ()
		{
			return string.Format("{0}{1}",face.ToChar(),suit.ToChar());
		}

        /// <summary>
        /// Obtain a string representation of the instance, using the specified format
        /// </summary>
        /// <param name="format">The type of format to be used</param>
        /// <returns>A string representation of this Card in the specified format</returns>
        public string ToString(CardFormat format)
        {
            switch (format)
            {
                case CardFormat.TWO_CHARACTERS:
                    return ToShortString();

                case CardFormat.FULL_NAME:
                    return string.Format("{0} of {1}", CardFace.ToString(), CardSuit.ToString());

                default:
                    throw new NotImplementedException();
            }
        }

		public override string ToString ()
		{
            return ToString(CardFormat.FULL_NAME);
		}

		#region Read Attribute Accessors
		public Face CardFace { get { return face; } }
		public Suit CardSuit { get { return suit; } }
		#endregion
	}
}

